12Factor, 全名是The Twelve-Factor App
, Heroku平台團隊彙整了這些實戰經驗並發表出來.
12 Factor能應用在微服務應用以及各種語言開發的應用上. 微服務應用的特徵是需要遵循一套設計規範.
在微服務下的開發者必須改變自己的開發習慣來滿足自動化
、容器化
和基礎設施管理
等要求.
12 Factor就是定義出一套用於這規劃下的新型契約.
這12 Factor內容包含了
- Codebase
- Dependencies
- Config
- Backing services
- Build, release, run
- Processes
- Port biding
- Concurrency
- Disposability
- Dev/prod parity
- Logs
- Admin processes
依序慢慢介紹
基本定義: 一份基準代碼Codebase, 對上多份佈署Deploy.
意思就是微服務的codebase都是相同的一份, 雖然可能會佈署多個版本到不同環境(開發、測試、產線)上, 同時也以不同的介質(Docker、VM...)做部屬.
Codebase是以代碼的方式被統一集中在Version control system中(Git、Subversion...), 這樣就能透過Git來追蹤每一個佈署出去的微服務應用.
基本定義: 顯式聲明依賴關係
- 對於每個微服務, 要聲明所有的依賴項目, 方便編譯工具能正確的編譯出微服務應用. 像是Java Maven的pom.xml, Go的go.mod, Node的package.json. 這是
編譯過程
的顯式聲明依賴關係.
- 業務微服務之間應該也要明確定義相互的依賴關係, 可以透過一些服務編排框架來達成. 昨天提到的Jaeger DAG也有點這味道.
基本定義: 在環境中儲存配置
- 代碼內要避免跟環境有關連的hard code. 像是代碼內有 if env == staging xxx else if env == prod yyy
- 微服務的開發跟交付分別對應兩個完全隔離的階段還有環境; 開發階段撰寫codebase, 交付階段負責管理環境變數. 這樣Git分支上, 就沒必要分一堆dev、test、prod, 哪天又多了staging、qa, 或者開發者自己的
ithome-dev
...這樣不同分支不同的config組合太複雜了.
- 因此config不應該以file形式放到Git上, 應該直接放置在環境變量上, 像是evn上. 又或者使用K8S的configmaps 或是MicroProfile Config, Puppet, Vargrant等等的, 讓GIT和代碼與config撥離. Docker則是能透過-e(env)來設定環境變數.
docker run -e "DATABASE=mongodb://localhost:27017" -e "SECRET=ithome" myapp
- 不然就是統一找config server要config, 之前介紹的etcd可以充當這角色. 每個環境獨立一組etcd server, 內部IP都一樣, 服務起來透過一樣的host:port來存取.
基本定義: 把後端服務作為附加資源
-
微服務鎖依賴的外部服務(需要透過網路調用的)都屬於依賴資源.
-
微服務鎖依賴的資源都是可以被移植, 且可以鬆散地整合其他類似的資源. 舉例: 服務依賴於MySQL的接口, 開發實用到本地的MySQL, 產線則可以遷移到AWS RDS上, code都不需要更動.
-
如果依賴的資源出現異常, 運維只需要卸載現在的MySQL, 並且掛載上新的MySQL實例, 最多也只需要動到config.
-
微服務的內部組件, 不應該直接依賴資源, 而是應該依賴其接口; 也只需要關注自己內部的實現, 然後也只需要了解外部資源提供的接口, 不用詳細了解外部依賴資源的具體實現.
基本定義: 嚴格分離Build, Release, Run
- Build stage是微服務應用透過自動或手動方式, 把code編譯成微服務組件(exe, binary)
- Release stage是把微服務組件發到對應的Registry(Docker-Registry, Harbor, Github release tag)上, 提供給外部運行環境來下載
- Run stage把微服務組件從Registry拉下來+第三點的config env, 佈署到對應的運行環境上.
- 盡可能透過CI/CD工具來自動化完成上面三個階段
- 每一個發布的版本都應該要有唯一的版本發布ID
基本定義: 以一個或是多個無狀態
進程運行微服務應用
-
DAY3有提到什麼是
Stateless無狀態
- 微服務組件在開發設計時, 應該考慮到隨時會失敗或是crash的情況來設計. 這種設計可以實現微服務進程隨時被建立或是被Kill, 尤其在Auto-Scale in/out階段.
基本定義: 透過Port綁定來提供服務
- 微服務組件透過Port來提供服務. 不同的Port綁定不同的請求協議(Http、rpc、redis、mysql...), 並監聽所有發送到該端口的請求.
- 每個微服務組件應該透過服務發現機制(Day20提到)對外提供服務
基本定義: 服務透過Process模型進行水平擴展
- 微服務應用本身只是一個process, 但業務分支可以內部開啟多個Thread/coroutine來分散prcess性能瓶頸.
- 雷同餘上面的Prcess觀點, 同一個服務功能, 可以起對多個微服務實例(一個實例就是一個process), 達到水平擴展.
- 透過添加無狀態process進行水平擴展, 也是上面提過的觀點.
基本定義: 快速啟動、優雅終止, 可最大化系統可容忍干擾的能力(Robustness)
- 微服務啟動時, 可以透過Lazy Loading的方式實現快速啟動.
- 微服務終止時硬確保所有資源已經釋放(db連線...), 所有監聽也關閉(等待還沒完成的請求結束, 且不再接受新的請求), 然後優雅地關閉進程.
- 當非正常結束時(crash), 微服務會自動恢復到默認狀態.
-
服務熔斷跟降級的應用
基本定義: 環境等價, 盡可能保持dev、pre-release、prod環境相同
- 保持微服務的dev、pre-release、prod盡可能相似, 這樣可以保證應用的高品質、持續交付跟佈署
- 透過Docker來減少因環境不同帶來的排錯等成本溝通問題, 否則就要用Adapter來支持差異性
- 對於後台資料, 差異性的存在是不可避免的, 像是報表資料, 開發環境就不是很多甚至沒有, 這時可能就要有一些機制來隔離
- 最好開發&運維一體化, 同一批人, 減少溝通成本.
基本定義: 將Log視為Event streams(事件流)
- 微服務應該建立統一的服務來管理日誌(ELK、Graylog、Cloudwatch...)
- 每個運行的微服務都直接透過Stdout和Stderr來輸出日誌; 開發時期透過console來查看時間排序下的Event log; 上到環境後, 則是透過集中服務, 來收集、聚合、索引、分析這些Log
- 採用分布式追蹤服務(Day29提到)來解決微服務之間調用的問題
- 透過Log對微服務進行監控(Prometheus, Jaeger...)
- 把Event stream透過Splunk做分析, 或是存放在Hadoop做儲存查找分析.
基本定義: 把維護/管理任務當作一次性process執行
- 後台維護任務通常跟業務應用本身的技術框架還有作法不太一樣, 所以盡可能實現上是分離的.
- 微服務管理或是系統的維護是應用維護的基礎部份, 要作為一次性prcess來執行; 像是db migration, db snapshot, schema/table更動, 定期renew憑證等等的.
- 把這些一次性作業寫成腳本或是Shell, 當成一般程式來執行, 也進到Git做版控.
- 也能把這些場景變成微服務模式來實現
部份內容參考小弟以前的文章